Skip to content

feat: tag feeds#3875

Merged
capJavert merged 4 commits into
mainfrom
tag-feeds
May 14, 2026
Merged

feat: tag feeds#3875
capJavert merged 4 commits into
mainfrom
tag-feeds

Conversation

@capJavert
Copy link
Copy Markdown
Contributor

@capJavert capJavert commented May 14, 2026

Add tag based feed endpoint for explore strip to create feeds per tag(s).

Due to new feed endpoints I refactored all *Feed secrets to be just base urls http://feed.service.local instead of http://feed.service.local/api/feed, this way path is added at method call time (feedClient.fetchFeed) and allows different methods to have different paths.

@capJavert capJavert self-assigned this May 14, 2026
@pulumi
Copy link
Copy Markdown

pulumi Bot commented May 14, 2026

🍹 The Update (preview) for dailydotdev/api/prod (at 048080b) was successful.

✨ Neo Explanation

Standard application deployment rolling out feed URL restructuring and two new feed GraphQL endpoints (`feedByTags`, `feedTagsList`), with a Kubernetes Secret replacement due to env var value changes. ✅ Low Risk overall, though the secret replacement warrants a brief timing check to ensure pods don't start before the new secret is available.

This deployment rolls out a new application version (e6b5487c4033c0d9) that introduces two new GraphQL queries (feedByTags, feedTagsList), migrates feed service URLs from path-embedded endpoints (/feed.json) to a base-URL + path pattern (/api/feed, /api/personalised, /api/user_tags), and adds a feedTagsList cache field to User.flags. The Pulumi config changes (internalFeed and personalizedDigestFeed dropping the path suffix) directly reflect this URL restructuring.

🟡 Warning — The vpc-native-k8s-secret is being replaced (delete + create) due to changed secret data. The two env vars INTERNAL_FEED and PERSONALIZED_DIGEST_FEED are changing from http://feed.feed.svc.cluster.local/api/feed?http://feed.feed.svc.cluster.local and http://feed-digest-server.feed/api/personalisedhttp://feed-digest-server.feed respectively. Any pods that mount this secret by reference will temporarily reference the old secret until the replacement completes and a rolling restart propagates the new values — however, since all deployments are also being updated to the new image (which constructs the full URL in code), the timing should be fine as long as the secret replacement completes before pods start.

🔵 Info — Both a DB migration job and a Clickhouse migration job are being cycled for the new commit hash, indicating this release includes schema changes (likely adding/modifying the feedTagsList field on User.flags, though JSONB flags columns typically don't require DDL).

Resource Changes

    Name                                                       Type                           Operation
~   vpc-native-rotate-daily-quests-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-expire-super-agent-trial-cron                   kubernetes:batch/v1:CronJob    update
-   vpc-native-api-db-migration-e6b5487c                       kubernetes:batch/v1:Job        delete
~   vpc-native-post-analytics-clickhouse-cron                  kubernetes:batch/v1:CronJob    update
~   vpc-native-update-views-cron                               kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-zombie-opportunities-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-post-analytics-history-day-clickhouse-cron      kubernetes:batch/v1:CronJob    update
~   vpc-native-bg-deployment                                   kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-zombie-images-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-update-highlighted-views-cron                   kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-cron                        kubernetes:batch/v1:CronJob    update
~   vpc-native-deployment                                      kubernetes:apps/v1:Deployment  update
~   vpc-native-user-posts-analytics-refresh-cron               kubernetes:batch/v1:CronJob    update
~   vpc-native-rotate-weekly-quests-cron                       kubernetes:batch/v1:CronJob    update
~   vpc-native-squad-posts-analytics-refresh-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-update-achievement-rarity-cron                  kubernetes:batch/v1:CronJob    update
-   vpc-native-api-clickhouse-migration-e6b5487c               kubernetes:batch/v1:Job        delete
~   vpc-native-sync-subscription-with-cio-cron                 kubernetes:batch/v1:CronJob    update
~   vpc-native-user-profile-analytics-history-clickhouse-cron  kubernetes:batch/v1:CronJob    update
+-  vpc-native-k8s-secret                                      kubernetes:core/v1:Secret      create-replacement
~   vpc-native-validate-active-users-cron                      kubernetes:batch/v1:CronJob    update
~   vpc-native-update-current-streak-cron                      kubernetes:batch/v1:CronJob    update
~   vpc-native-temporal-deployment                             kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-stale-user-transactions-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-update-source-public-threshold-cron             kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-gifted-plus-cron                          kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-expired-better-auth-sessions-cron         kubernetes:batch/v1:CronJob    update
~   vpc-native-materialize-monthly-best-post-archives-cron     kubernetes:batch/v1:CronJob    update
~   vpc-native-personalized-digest-deployment                  kubernetes:apps/v1:Deployment  update
~   vpc-native-channel-digests-cron                            kubernetes:batch/v1:CronJob    update
~   vpc-native-worker-job-deployment                           kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-zombie-users-cron                         kubernetes:batch/v1:CronJob    update
~   vpc-native-private-deployment                              kubernetes:apps/v1:Deployment  update
~   vpc-native-clean-zombie-user-companies-cron                kubernetes:batch/v1:CronJob    update
~   vpc-native-update-tag-materialized-views-cron              kubernetes:batch/v1:CronJob    update
~   vpc-native-clean-channel-highlights-cron                   kubernetes:batch/v1:CronJob    update
~   vpc-native-ws-deployment                                   kubernetes:apps/v1:Deployment  update
~   vpc-native-materialize-yearly-best-post-archives-cron      kubernetes:batch/v1:CronJob    update
~   vpc-native-user-profile-analytics-clickhouse-cron          kubernetes:batch/v1:CronJob    update
~   vpc-native-calculate-top-readers-cron                      kubernetes:batch/v1:CronJob    update
~   vpc-native-generate-search-invites-cron                    kubernetes:batch/v1:CronJob    update
... and 12 other changes

@capJavert
Copy link
Copy Markdown
Contributor Author

@claude review

@claude
Copy link
Copy Markdown

claude Bot commented May 14, 2026

Claude finished @capJavert's task in 3m 15s —— View job


Code review

Solid PR overall — the refactor from per-call feedId to a passed-in path is the right shape, and the new feedByTags reuses the standard for-you pipeline cleanly via withConfigTransform. A few things worth addressing before merge:

🔴 Should fix

  1. Cross-domain dependency in FeedClientsrc/integrations/feed/clients.ts:10 imports isMockEnabled from ../../mocks/opportunity/services. The feed client is a core integration; pulling it in via opportunity-specific code couples unrelated domains. isMockEnabled is just process.env.MOCK_EXTERNAL_SERVICES === 'true' — either move it to src/mocks/common.ts (or similar) and import from there, or inline the env check in the feed client. The MOCK_USER_TAGS constant block (clients.ts:11-22) probably belongs in src/mocks/feed/ next to it for the same reason.
    Fix this →

  2. Tag deduplication missing in getFeedTagsListsrc/common/feedTagsList.ts:53 does [...tags, ...supplementTags].slice(0, limit). If feedClient.getUserTags returns ['ai', 'ml'] and recswipe recommendTags also returns ['ai', 'pytorch'] (selectedTags filtering is the recswipe service's responsibility and shouldn't be relied on here), the user gets ['ai', 'ml', 'ai', 'pytorch']. Dedupe before slicing:

    tags = Array.from(new Set([...tags, ...supplementTags])).slice(0, limit);
  3. Stale JSDoc on IFeedClient.fetchFeedsrc/integrations/feed/types.ts:128 still says @param feedId The feed ID (used for caching primarily) after the parameter was renamed to path. Update the doc to reflect that the caller now supplies the request path (/api/feed, /api/personalised, etc.).

  4. Dead feedId field on FeedGenerator — Now that generate() hardcodes /api/feed (src/integrations/feed/generators.ts:43), the feedId constructor parameter is only ever stored and forwarded through withConfigTransform. Several callers still pass strings like 'channel' (src/schema/feeds.ts:1622) that go nowhere. Either remove the parameter or wire it back through if it's intentional.

🟡 Worth considering

  1. No upper bound on feedByTags.tagssrc/schema/feeds.ts:588 declares tags: [String!]! without a max length. A client passing thousands of tags will hit the downstream feed service unbounded. Consider validating with Zod (e.g., .max(20)) and rejecting empty arrays, similar to how feedTagsListInputSchema constrains its limit.

  2. Race on first-miss cache fill — two concurrent feedTagsList requests for the same user that both miss cache will both call getUserTags + recommendTags and both write to user.flags. Last write wins; functionally fine, but worth noting if the upstream services are expensive. Not a blocker.

  3. No graceful fallback when getUserTags fails — if the feed service /api/user_tags errors (5xx, timeout, garmr trip), the whole feedTagsList resolver rejects, even though recswipe alone could supply tags. Given this powers a navigation UI element, consider catching feed errors and falling back to recswipe-only.

  4. Cache returns fewer tags than requested without refetchfeedTagsList.ts:39-41: if the cache holds 5 tags and the client requests limit: 10, the cached path returns 5 and never refreshes (until 24h elapses). Probably acceptable, but if the FE bumps the limit you'll silently under-deliver.

  5. isFresh accepts future timestamps as freshDate.now() - ts < CACHE_TTL_MS is true for any ts in the future. Negligible in practice (clocks shouldn't go backward), but Math.abs or >= 0 guard would be more defensive.

🔵 Style / nits

  • FeedByTagsArgs is declared as interface (feeds.ts:1215); the project prefers type per CLAUDE.md, though the surrounding code (TagFeedArgs, KeywordFeedArgs) uses interface, so this matches local convention. Optional.
  • MOCK_USER_TAGS (10 tags) is sliced by limit, but the schema allows up to FEED_TAGS_LIST_MAX_LIMIT = 10, so it's exactly sized. Fine, just worth a one-line comment or making the relationship explicit if the limit ever bumps.
  • Pulumi.prod.yaml: encrypting internalFeed and personalizedDigestFeed is a create-replacement on the k8s secret (flagged by Neo). Brief deploy-time window where pods can't read the secret — schedule accordingly.

✅ Things done well

  • withConfigTransform is the right primitive — composing a tag override without re-implementing for-you logic is clean.
  • New tests cover auth, cache hit/miss/stale, limit validation, and recswipe backfill. The feed-service body assertions on /api/user_tags and /api/feed are tight.
  • Path-at-call-time is more flexible than the old INTERNAL_FEED=...feed.json shape and makes adding /api/personalised and /api/user_tags trivial.
  • Hoisting RECOMMENDED_TAGS_LIMIT / MIN_SEARCH_QUERY_LENGTH / SEARCH_TAGS_LIMIT from schema/tags.ts to src/types.ts removes the awkward schema → schema import.

· branch tag-feeds

@capJavert capJavert enabled auto-merge (squash) May 14, 2026 09:43
@capJavert capJavert merged commit 175a250 into main May 14, 2026
10 checks passed
@capJavert capJavert deleted the tag-feeds branch May 14, 2026 09:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant